/* Copyright (c) 2003-2007 Caleb Doise
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

using System;
using System.Runtime.InteropServices;


namespace SharpScraps.Win32
{


    public delegate void KeyboardHookCallback(int msg, int keyCode, out bool swallowMsg);
    //public delegate IntPtr ShellHookCallback(int nCode, IntPtr wParam, IntPtr lParam, out bool swallowMsg);


    internal delegate IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam);
    //internal delegate IntPtr ShellHookProc(int nCode, IntPtr wParam, IntPtr lParam);    



    public static class WindowsHooks
    {
        public const int KeyDownMessage = 256;
        public const int KeyUpMessage = 257;
     

        public static WindowsHookHandle RegisterKeyboardHook(
            KeyboardHookCallback hookCallback)
        {
            KeyboardHookCallbackCaller hookCaller = new KeyboardHookCallbackCaller(hookCallback);

            hookCaller.HookHandle = SetWindowsHookEx(
                WH_KEYBOARD_LL, 
                hookCaller.GetHookProc(), 
                Marshal.GetHINSTANCE(typeof(WindowsHooks).Module),
                0);

            return new WindowsHookHandle(hookCaller.HookHandle, hookCaller);
        } // RegisterKeyboardHook


        //public static WindowsHookHandle RegisterShellHook(
        //    ShellHookCallback hookCallback)
        //{
        //    // NOTE: This doesn't seem to work. Might have to implement it in unmanaged code.

        //    ShellHookCallbackCaller hookCaller = new ShellHookCallbackCaller(hookCallback);

        //    hookCaller.HookHandle = SetWindowsHookEx(
        //        WH_SHELL, 
        //        hookCaller.GetHookProc(), 
        //        Marshal.GetHINSTANCE(typeof(WindowsHooks).Module),
        //        0);

        //    return new WindowsHookHandle(hookCaller.HookHandle, hookCaller);
        //} // RegisterKeyboardHook


        [DllImport("User32.dll")]
        private static extern IntPtr SetWindowsHookEx(int idHook, KeyboardHookProc lpfn, IntPtr hMod, UInt32 dwThreadId);		

        //[DllImport("User32.dll")]
        //private static extern IntPtr SetWindowsHookEx(int idHook, ShellHookProc lpfn, IntPtr hMod, UInt32 dwThreadId);	

        [DllImport("User32.dll")]
        internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
	        
        private const int WH_MIN              = -1;
        private const int WH_MSGFILTER        = -1;
        private const int WH_JOURNALRECORD    = 0;
        private const int WH_JOURNALPLAYBACK  = 1;
        private const int WH_KEYBOARD         = 2;
        private const int WH_GETMESSAGE       = 3;
        private const int WH_CALLWNDPROC      = 4;
        private const int WH_CBT              = 5;
        private const int WH_SYSMSGFILTER     = 6;
        private const int WH_MOUSE            = 7;
        private const int WH_HARDWARE         = 8;
        private const int WH_DEBUG            = 9;
        private const int WH_SHELL            = 10;
        private const int WH_FOREGROUNDIDLE   = 11;
        private const int WH_CALLWNDPROCRET   = 12;
        private const int WH_KEYBOARD_LL      = 13;
        private const int WH_MOUSE_LL         = 14;


        #region Constants

        public static class ShellConstants
        {
            public const int HSHELL_WINDOWCREATED        = 1;
            public const int HSHELL_WINDOWDESTROYED      = 2;
            public const int HSHELL_ACTIVATESHELLWINDOW  = 3;

            public const int HSHELL_WINDOWACTIVATED      = 4;
            public const int HSHELL_GETMINRECT           = 5;
            public const int HSHELL_REDRAW               = 6;
            public const int HSHELL_TASKMAN              = 7;
            public const int HSHELL_LANGUAGE             = 8;
            public const int HSHELL_SYSMENU              = 9;
            public const int HSHELL_ENDTASK              = 10;

            public const int HSHELL_ACCESSIBILITYSTATE   = 11;
            public const int HSHELL_APPCOMMAND           = 12;

            public const int HSHELL_WINDOWREPLACED       = 13;
            public const int HSHELL_WINDOWREPLACING      = 14;

            public const int HSHELL_HIGHBIT              = 0x8000;
            public const int HSHELL_FLASH                = (HSHELL_REDRAW | HSHELL_HIGHBIT);
            public const int HSHELL_RUDEAPPACTIVATED     = (HSHELL_WINDOWACTIVATED | HSHELL_HIGHBIT);
        } // class ShellConstants 

        #endregion

    } // class WindowsHooks



    internal class KeyboardHookCallbackCaller
    {
        private IntPtr hHook = IntPtr.Zero;
        private KeyboardHookCallback callback;
        private KeyboardHookProc hookProc;

        internal KeyboardHookCallbackCaller(KeyboardHookCallback callback)
        {
            this.callback = callback;
            this.hookProc = new KeyboardHookProc(this.HookProc);
        }

        internal IntPtr HookHandle
        {
            get { return this.hHook; }
            set { this.hHook = value; }
        }

        internal KeyboardHookProc GetHookProc()
        {
            return this.hookProc;
        }

        private IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            bool swallowMsg = false;

            try
            {
                int msg = (int)wParam;
                int keyCode = Marshal.ReadInt32(lParam);

                this.callback(msg, keyCode, out swallowMsg);
            }
            catch
            {
                swallowMsg = false;
            }

            if (!swallowMsg && (this.hHook != IntPtr.Zero))
                return CallNextHookEx(this.hHook, nCode, wParam, lParam);
            else
                return new IntPtr(1);
        } // HookProc

        [DllImport("User32.dll")]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    } // class KeyboardHookCallbackCaller


    //internal class ShellHookCallbackCaller
    //{
    //    private IntPtr hHook = IntPtr.Zero;
    //    private ShellHookCallback callback;
    //    private ShellHookProc hookProc;

    //    internal ShellHookCallbackCaller(ShellHookCallback callback)
    //    {
    //        this.callback = callback;
    //        this.hookProc = new ShellHookProc(this.HookProc);
    //    }

    //    internal IntPtr HookHandle
    //    {
    //        get { return this.hHook; }
    //        set { this.hHook = value; }
    //    }

    //    internal ShellHookProc GetHookProc()
    //    {
    //        return this.hookProc;
    //    }

    //    private IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam)
    //    {
    //        IntPtr result = IntPtr.Zero;
    //        bool swallowMsg = false;

    //        try
    //        {
    //            int msg = (int)wParam;
    //            int keyCode = Marshal.ReadInt32(lParam);

    //            result = this.callback(msg, wParam, lParam, out swallowMsg);
    //        }
    //        catch
    //        {
    //            swallowMsg = false;
    //        }

    //        if (!swallowMsg && (this.hHook != IntPtr.Zero))
    //            return WindowsHooks.CallNextHookEx(this.hHook, nCode, wParam, lParam);
    //        else
    //            return result;
    //    } // HookProc      

    //} // class ShellHookCallbackCaller



    public class WindowsHookHandle : IDisposable
    {
        private IntPtr hHook;
        private object hookCallbackCaller;

        internal WindowsHookHandle(IntPtr hHook, object hookCallbackCaller)
        {
            this.hHook = hHook;
            this.hookCallbackCaller = hookCallbackCaller;
        } // WindowsHookHandle


        ~WindowsHookHandle()
        {
            if (this.hHook != IntPtr.Zero)
            {
                UnhookWindowsHookEx(this.hHook);
                this.hHook = IntPtr.Zero;
            }
        } // ~WindowsHookHandle


        public void Dispose()
        {
            if (this.hHook != IntPtr.Zero)
            {
                UnhookWindowsHookEx(this.hHook);
                this.hHook = IntPtr.Zero;
            }
        } // Dispose

        [DllImport("User32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    } // class WindowsHookHandle



} // namespace SharpScraps.Win32